package com.tianyi.controller.wechat;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Maps;
//import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;
import org.apache.commons.codec.binary.Base64;
import com.tianyi.config.WeChatConfig;
import com.tianyi.lib.bo.wechat.AuthJsToken;
import com.tianyi.lib.bo.wechat.WeChatPhoneBo;
import com.tianyi.lib.dto.WeChatPayDTO;
import com.tianyi.lib.model.WebResult;
import com.tianyi.util.CommUtils;
import com.tianyi.util.PayUtil;
import com.tianyi.wechat.WeChatPayService;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.weixin4j.WeixinException;
import org.weixin4j.WeixinSupport;
import org.weixin4j.http.HttpsClient;
import org.weixin4j.http.Response;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.math.BigDecimal;
import java.security.spec.AlgorithmParameterSpec;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * @Description: 本示例仅供参考，请根据自己的使用情景进行修改
 * @Date: 2018/4/8
 * @Author: wcf
 */
@RequestMapping("/wechartpay")
@RestController
public class WeixinController extends WeixinSupport{

    private Logger logger = LoggerFactory.getLogger(WeixinController.class);

    /**
     * 微信小程序appid
     */
    private static final String appid = "wxa72888c497ae8cef";
    /**
     * 微信小程序密钥
     */
    private static final String secret = "71fb1c8e692aed6110facbea4e88fbcb";
    private static final String grant_type = "authorization_code";

//    @Autowired
//    private WxCashService wxCashService;
//    @Autowired
//    private ClothesApplyService clothesApplyService;

    @Autowired
    private WeChatPayService weChatPayService;

    /**
     * 根据code获取open_id
     * 小程序后台登录，向微信平台发送获取access_token请求，并返回openId
     *
     * @param code
     * @return openid
     * @throws WeixinException
     * @throws IOException
     * @since Weixin4J 1.0.0
     * @see WeChatController#login(java.lang.String, javax.servlet.http.HttpServletRequest)
     */
    @Deprecated
    @GetMapping("login")
    public Map<String, Object> login(String code, HttpServletRequest request) throws WeixinException, IOException {
        if (StringUtils.isBlank(code)) {
            throw new WeixinException("invalid null, code is null.");
        }

        Map<String, Object> result = Maps.newHashMap();
        //拼接参数
        String param = getWeChartOpenIdParam(code);
        //创建请求对象
        HttpsClient http = new HttpsClient();
        Response res =null;
        try{
            //调用获取access_token接口
            res = http.get("https://api.weixin.qq.com/sns/jscode2session" + param);
            //根据请求结果判定，是否验证成功
            JSONObject jsonObj = res.asJSONObject();
            if (Objects.isNull(jsonObj)) {

                return result;
            }
            Object errcode = jsonObj.get("errcode");
            if (Objects.nonNull(errcode)) {
                //返回异常信息
                throw new WeixinException(getCause(Integer.parseInt(errcode.toString())));
            }
            AuthJsToken authJsToken = JSON.parseObject(jsonObj.toJSONString(), AuthJsToken.class);
            logger.info("openid={} and session_key={}", authJsToken.getOpenid(), authJsToken.getSession_key());
            result.put("openid", authJsToken.getOpenid());
            result.put("session_key", authJsToken.getSession_key());
        }catch (Exception e){
            e.printStackTrace();
        }
        return result;
    }

    /**
     * 根据code获取构建发微信api的参数
     * @param code
     * @return
     */
    private String getWeChartOpenIdParam(String code){

        StringBuilder builder = new StringBuilder();
        builder.append("?grant_type=").append(grant_type)
                .append("&appid=" ).append(appid)
                .append("&secret=").append(secret)
                .append("&js_code=").append(code);
        return builder.toString();
    }

    public String buildWeChatPayXML(WeChatPayDTO weChatPayDTO, String tempRandomStr, String orderNo,
                                    String currentIp, BigDecimal money, String mysign){

//        String xml = "<xml>" + "<appid>" + WeChatConfig.appid + "</appid>"
//                + "<body><![CDATA[" + weChatPayDTO.getBody() + "]]></body>"
//                + "<mch_id>" + WeChatConfig.mch_id + "</mch_id>"
//                + "<nonce_str>" + tempRandomStr + "</nonce_str>"
//                + "<notify_url>" + WeChatConfig.notify_url + "</notify_url>"
//                + "<openid>" + weChatPayDTO.getOpenid() + "</openid>"
//                + "<out_trade_no>" + orderNo + "</out_trade_no>"
//                + "<spbill_create_ip>" + currentIp + "</spbill_create_ip>"
//                + "<total_fee>" + money + "</total_fee>"
//                + "<trade_type>" + WeChatConfig.TRADETYPE + "</trade_type>"
//                + "<sign>" + mysign + "</sign>"
//                + "</xml>";
        StringBuilder builder = new StringBuilder();

        builder.append("<xml>" )
                .append("<appid>")
                .append(WeChatConfig.appid)
                .append("</appid>")
                .append("<body><![CDATA[")
                .append(weChatPayDTO.getBody())
                .append("]]></body>")
                .append("<mch_id>")
                .append(WeChatConfig.mch_id)
                .append("</mch_id>")
                .append("<nonce_str>")
                .append(tempRandomStr)
                .append("</nonce_str>")
                .append("<notify_url>")
                .append(WeChatConfig.notify_url)
                .append("</notify_url>")
                .append("<openid>")
                .append(weChatPayDTO.getOpenid())
                .append("</openid>")
                .append("<out_trade_no>")
                .append(orderNo)
                .append("</out_trade_no>")
                .append("<spbill_create_ip>")
                .append(currentIp)
                .append("</spbill_create_ip>")
                .append("<total_fee>")
                .append(money)
                .append("</total_fee>")
                .append("<trade_type>")
                .append(WeChatConfig.TRADETYPE)
                .append("</trade_type>")
                .append("<sign>")
                .append(mysign)
                .append("</sign>")
                .append("</xml>");

        return builder.toString();
    }


    /**
     * @Description: 发起微信支付
     * @param weChatPayDTO
     * @param request
     * @author: wcf
     * @date: 2019.02.14
     */
    @GetMapping("wxPay")
    public WebResult<?> wxPay(WeChatPayDTO weChatPayDTO, HttpServletRequest request){
        try{
            //生成的随机字符串
            String tempRandomStr = weChatPayService.getRandomStringByLength(32);
            //商品名称
            String body = weChatPayDTO.getBody();
            //获取本机的ip地址
            String currentIp = CommUtils.getIpAddr(request);
            String orderNo = weChatPayDTO.getOrderNo();
            //支付金额，单位：分，这边需要转成字符串类型，否则后面的签名会失败
            BigDecimal money = weChatPayDTO.getMoney();

            //todo 这里最好封装一下
            Map<String, String> packageParams = new HashMap<String, String>();
            packageParams.put("appid", WeChatConfig.appid);
            packageParams.put("mch_id", WeChatConfig.mch_id);
            packageParams.put("nonce_str", tempRandomStr);
            packageParams.put("body", body);
            //商户订单号
            packageParams.put("out_trade_no", orderNo);
            //支付金额，这边需要转成字符串类型，否则后面的签名会失败
            packageParams.put("total_fee", money.toString());
            packageParams.put("spbill_create_ip", currentIp);
            packageParams.put("notify_url", WeChatConfig.notify_url);
            packageParams.put("trade_type", WeChatConfig.TRADETYPE);
            packageParams.put("openid", weChatPayDTO.getOpenid());
            // 除去数组中的空值和签名参数
            packageParams = PayUtil.paraFilter(packageParams);

            // 把数组所有元素，按照“参数=参数值”的模式用“&”字符拼接成字符串
            String presStr = PayUtil.createLinkString(packageParams);

            //MD5运算生成签名，这里是第一次签名，用于调用统一下单接口
            String mysign = PayUtil.sign(presStr, WeChatConfig.key, "utf-8").toUpperCase();
            logger.info("=======================第一次签名：" + mysign + "=====================");
            //拼接统一下单接口使用的xml数据，要将上一步生成的签名一起拼接进去

            String payXML = buildWeChatPayXML(weChatPayDTO, tempRandomStr, orderNo, currentIp, money, mysign);

            logger.info("调试模式_统一下单接口 请求XML数据：{}" + payXML);

            //调用统一下单接口，并接受返回的结果
            String result = PayUtil.httpRequest(WeChatConfig.pay_url, "POST", payXML);
            logger.info("调试模式_统一下单接口 返回XML数据：" + result);

            // 将解析结果存储在HashMap中
            Map<String, String> map = PayUtil.doXMLParse(result);
            //返回状态码
            String return_code = map.get("return_code");

            //返回给移动端需要的参数
            Map<String, Object> response = new HashMap<String, Object>();

            //todo fix here
            if(return_code == "SUCCESS" || return_code.equals(return_code)){
                // 业务结果
                //返回的预付单信息
                String prepay_id = map.get("prepay_id");
                response.put("nonceStr", tempRandomStr);
                response.put("package", "prepay_id=" + prepay_id);
                Long timeStamp = System.currentTimeMillis() / 1000;
                //这边要将返回的时间戳转化成字符串，不然小程序端调用wx.requestPayment方法会报签名错误
                response.put("timeStamp", timeStamp + "");
                String stringSignTemp = "appId=" + WeChatConfig.appid + "&nonceStr=" + tempRandomStr + "&package=prepay_id=" + prepay_id+ "&signType=" + WeChatConfig.SIGNTYPE + "&timeStamp=" + timeStamp;
                //再次签名，这个签名用于小程序端调用wx.requesetPayment方法
                String paySign = PayUtil.sign(stringSignTemp, WeChatConfig.key, "utf-8").toUpperCase();
                logger.info("=======================第二次签名：" + paySign + "=====================");
                response.put("paySign", paySign);
                //更新订单信息
                //业务逻辑代码
            }
            response.put("appid", WeChatConfig.appid);
            return WebResult.ok(response);
        }catch(Exception e){
            e.printStackTrace();
            return WebResult.error("发起失败");
        }
//        System.out.println("签名内容"+json);
//        return json;
    }

    /**
     * @Description:微信支付
     * @return
     * @author dzg
     * @throws Exception
     * @throws WeixinException
     * @date 2019.02.14
     */
    @GetMapping(value="/wxNotify")
    public void wxNotify(HttpServletRequest request, HttpServletResponse response) throws Exception{
        BufferedReader br = new BufferedReader(new InputStreamReader((ServletInputStream)request.getInputStream()));
        String line = null;
        StringBuilder sb = new StringBuilder();
        while((line = br.readLine())!=null){
            sb.append(line);
        }
        br.close();
        //sb为微信返回的xml
        String notityXml = sb.toString();
        String resXml = "";
        System.out.println("receive wxpay result：" + notityXml);
        Map<String, String> map = PayUtil.doXMLParse(notityXml);
        String returnCode = map.get("return_code");
        if("SUCCESS".equals(returnCode)){
            //验证签名是否正确
            System.out.println("签名验证结果1="+PayUtil.isSignatureValid(map,WeChatConfig.key));
            System.out.println("签名验证结果2="+PayUtil.verify(PayUtil.createLinkString(map), map.get("sign"), WeChatConfig.key, "utf-8"));
            if(PayUtil.isSignatureValid(map,WeChatConfig.key)){
                System.out.println("签名有效");
                /**此处添加自己的业务逻辑代码start**/
                //插入微信支付的业务表
//                WxCash wxCash=new WxCash();
//                wxCash.setId(Uuid.getUuid());
//                wxCash.setOrderno(map.get("out_trade_no").toString());
//                wxCash.setTransactionid(map.get("transaction_id").toString());
//                wxCash.setOpenid(map.get("openid").toString());
//                wxCash.setTotalfee(map.get("total_fee").toString());
//                wxCash.setTradetype(map.get("trade_type").toString());
//                wxCash.setBanktype(map.get("bank_type").toString());
//                //1 为支付成功
//                wxCash.setExt1(map.get("time_end").toString());
//                wxCash.setStatus("1");
//                wxCash.setCreatetime(CommonUtil.getInstance().getTime());
//                wxCash.setExt1(map.toString());
//                wxCashService.insert(wxCash);
//                //更新对应业务的状态
//                clothesApplyService.updateStatus("1",wxCash.getOrderno());
                /**此处添加自己的业务逻辑代码end**/
                //通知微信服务器已经支付成功
                resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>"
                        + "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";
            }
            else {
                System.out.println("签名无效");
            }
        }else{
            resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>"
                    + "<return_msg><![CDATA[报文为空]]></return_msg>" + "</xml> ";
        }
        System.out.println("返回结果"+resXml);
        System.out.println("微信支付回调数据结束");
        BufferedOutputStream out = new BufferedOutputStream(
                response.getOutputStream());
        out.write(resXml.getBytes());
        out.flush();
        out.close();
    }

    @RequestMapping(value = "deciphering", method = RequestMethod.GET)
    @ResponseBody
    public WeChatPhoneBo deciphering(String encryptedData, String iv, String sessionKey) {
        Base64 base64 = new Base64();
        byte[] encrypData = base64.decode(encryptedData);
        byte[] ivData = base64.decode(iv);
        byte[] sessionKeyByte = base64.decode(sessionKey);
        WeChatPhoneBo weChatPhoneBo = null;
        try {
            String str = decrypt(sessionKeyByte,ivData,encrypData);
            logger.info("deciphering decrypt str={}", str);
            weChatPhoneBo = JSON.parseObject(str, WeChatPhoneBo.class);
        } catch (Exception e) {
            e.printStackTrace();
            logger.info("deciphering error {}", e.getMessage());
        }
        return weChatPhoneBo;
    }

    public String decrypt(byte[] key, byte[] iv, byte[] encData) throws Exception {
        AlgorithmParameterSpec ivSpec = new IvParameterSpec(iv);
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
        cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
        //解析解密后的字符串  
        return new String(cipher.doFinal(encData),"UTF-8");
    }
}
